//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <wtypes.h>
#include <ntimage.h>
#include <bsp.h>
#include <loadmisc.h>
#include <bulverde.h>

// start kernel image address snooze buffer
#define HwBufferAddress     0xa0020000

//dummy
//#undef HwFlashBase
//#define HwFlashBase         0x05000000

//==========================================================================
/*
 * LNOTENOTE: Interface to loader and zeeldr
 */
typedef struct {
    UINT32  LoadAddress;
    UINT32  PackBase;
    UINT32  PackSize;/*size of the pack which had been loaded in ram*/
    UINT32  MemorySize;
    UINT32  WriteMagic;
    UINT32  Reserved1;
    UINT32  Reserved2;
    UINT32  Reserved3;
} OSLDRINFO;

typedef struct PackageInfo {
    unsigned int    uReserved;
    void            *modules;
    unsigned int    uNumberOfModules;
    unsigned int    uPackageSize;
    char            *szCommandLine;
} PackageInfo;

typedef struct {
    unsigned long modinfo;
    int     modcount;
    unsigned long packsize;
    unsigned long reseved;
}MODINFO_HEADER;

#define ENTRYPOINT(_b_) ((IMAGE_NT_HEADERS *)((_b_) + ((IMAGE_DOS_HEADER *)(_b_))->e_lfanew)) \
                          ->OptionalHeader.AddressOfEntryPoint
#define IMAGEBASE(_b_) ((IMAGE_NT_HEADERS *)((_b_) + ((IMAGE_DOS_HEADER *)(_b_))->e_lfanew)) \
                          ->OptionalHeader.ImageBase

#define IMAGESIZE(_b_) ((IMAGE_NT_HEADERS *)((_b_) + ((IMAGE_DOS_HEADER *)(_b_))->e_lfanew)) \
                          ->OptionalHeader.SizeOfImage

#define NTSIGNATURE(_b_) ((IMAGE_NT_HEADERS *)((_b_) + ((IMAGE_DOS_HEADER *)(_b_))->e_lfanew)) \
                          ->Signature

#define PMODINFO_HEADER(_b_) ((MODINFO_HEADER *)( ((_b_)) + (sizeof(IMAGE_DOS_HEADER)-sizeof(MODINFO_HEADER)) ) )

typedef void (__cdecl *ENTRYPOINT)(...);

// read a char , if timeout return 0
int ReadByteWithTimeout()
{
    int delay = 0x1000;
    int c;

    do {
        if ( (c=DBG_RecvChar()) >= 0 ) break;
    } while (delay-- > 0);

    return c;
}

// read a word no wait, if timeout return 0
unsigned long ReadWordNoWait(void)
{
    unsigned long x = 0;
    int c;
    if ( (c=ReadByteWithTimeout()) < 0) return 0;
    x = c;
    if ( (c=ReadByteWithTimeout()) < 0) return 0;
    x = x | ((unsigned long)c << 8);
    if ( (c=ReadByteWithTimeout()) < 0) return 0;
    x = x | ((unsigned long)c << 16);
    if ( (c=ReadByteWithTimeout()) < 0) return 0;
    x = x | ((unsigned long)c << 24);
    return x;
}

EXTERN_C int ReadCLKCFG(int);
EXTERN_C int WriteCLKCFG(int);
EXTERN_C void fmemcpy(void * pDest, const void * pSource, unsigned long cb);

#define volt_GPIO116    (1<<22)
#define volt_GPIO120    (1<<24)
#define VOLT_MIN        0x0
#define VOLT_LOW        0x1
#define VOLT_HIGH       0x2
#define VOLT_MAX        0x3

struct core_freq
{
    unsigned int mHZ;
    unsigned int L;
    unsigned int twoN;
    unsigned int turbo;
    unsigned int b;
    unsigned int volt;
} freq_table []= {
    {13, 1, 2, 1, 1, VOLT_MIN},
    {104, 8, 2, 0, 1, VOLT_LOW},
    //{156, 8, 6, 1, 1, VOLT_LOW},
    {208, 16, 2, 0, 1, VOLT_HIGH},
    //{104, 8, 6, 1, 1, VOLT_HIGH }, //312/104/104/104
    {208, 16, 3, 1, 1, VOLT_HIGH}, //312/208/208/104
    //{208, 16, 4, 1, 1, VOLT_MAX} //416/208/208/104
    };

void Voltage_GPIO(void)
{
    GPDR3 |= volt_GPIO116|volt_GPIO120;
    GPCR3 |= volt_GPIO116|volt_GPIO120;
}
void ChangeVoltage(unsigned long mode)
{
    switch (mode) {
        case VOLT_MIN:
            GPCR3 |=volt_GPIO116|volt_GPIO120;
            break;
        case VOLT_LOW:
            GPSR3 |= volt_GPIO116;
            GPCR3 |= volt_GPIO120;
            break;
        case VOLT_HIGH:
            GPSR3 |=volt_GPIO120;
            GPCR3 |= volt_GPIO116;
            break;
        case VOLT_MAX:
            GPSR3 |=volt_GPIO116|volt_GPIO120;
            break;
        default:
            //SerialOutputString("Can not surpport such change\n");
            break;
    }

    //Delay(100); //wait for the voltage to be stable
}

void ChangeFreq(struct core_freq freq)
{
    int value;
    unsigned long mdrefr_val, cccr_val = 0,clkcfg_val = 0, msc0_val;
    unsigned long L, M = 0, N2, memclk;

    value = CCCR;
    value &= 0x3FFFFFFF;                //make sure the Core and Peripheral PLL disable bits are clear.
    value = value & 0xFFFFF000;         //clear the L and 2N bits

    L = freq.L & 0x1F;                      //make sure L is only 0 thru 31
    value |= L;                         //OR in the L value

    N2 = freq.twoN & 0xf;                   //make sure 2N is only 0 thru 15
    value |= (N2 << 7);             //OR in the 2N value
    value |=CCCR_A;
    CCCR = value;                   //now set the CCCR register

    /* default setting */
    msc0_val    = MSC0;
    mdrefr_val  = MDREFR;

    /* MEM clock frequency = System Bus Frequence */
    if (freq.b)
        memclk = (L*13);
    else
        memclk = (L*13)>>1;///2;

    if (memclk <= 52) {
        /* SDCLK0 = MEMCLK */
        mdrefr_val &= ~(MDREFR_K0DB2 | MDREFR_K0DB4);
    }
    else if (memclk <= 104) {
        /* SDCLK0 = MEMCLK/2 */
        mdrefr_val &= ~MDREFR_K0DB4;
        mdrefr_val |= MDREFR_K0DB2;
    }
    else {
        /* SDCLK0 = MEMCLK/4 */
        mdrefr_val |= MDREFR_K0DB4;

        /* SDCLK1 = MEMCLK/2 - forced */
        mdrefr_val |= MDREFR_K1DB2;
    }

    /* Adjust MSC0 for Higher Memclks
     *
     * J3     : RRR = 5, RDN = 10
     * K3/K18 : RDF = 14
     * L3/L18 : Same as J3 for now, will be fixed later
     */

    /* adjust MSC0 for bank 0 */
    //STRATA_L18:
    {
        msc0_val &= ~(0x7f00);
        msc0_val |= 0x5a00;
    }

    /* adjust MSC0 for bank1 */
    //STRATA_L18:
    {
        msc0_val &= ~(0x7f00<<16);
        msc0_val |= (0x5a00<<16);
    }

    MSC0 = msc0_val;
    (void) MSC0;

    MDREFR = mdrefr_val;
    (void) MDREFR;
    MDREFR = (MDREFR & 0x3FDFEFFF);
    (void) MDREFR;

    if (freq.turbo == 0 && freq.b == 0) {
        //asm("mov  r0, #0x2");                 //set to Frequency Change Mode
        //asm("mcr  p14, 0, r0, c6, c0, 0");    //write to the CLKCFG coprocessor reg
        WriteCLKCFG(0x2);
    }

    if (freq.turbo == 0 && freq.b != 0) {
        //asm("mov  r0, #0xA");                 //set to Frequency Change Mode
        //asm("mcr  p14, 0, r0, c6, c0, 0");    //write to the CLKCFG coprocessor reg
        WriteCLKCFG(0xA);
    }

    if (freq.turbo != 0 && freq.b == 0) {
        //asm("mov  r0, #0x3");                 //set to Frequency Change Mode
        //asm("mcr  p14, 0, r0, c6, c0, 0");    //write to the CLKCFG coprocessor reg
        WriteCLKCFG(0x3);
    }

    if (freq.turbo != 0 && freq.b != 0) {
        //asm("mov  r0, #0xB");                 //set to Frequency Change Mode
        //asm("mcr  p14, 0, r0, c6, c0, 0");    //write to the CLKCFG coprocessor reg
        WriteCLKCFG(0xB);
    }

    //Delay(1000); //wait for it to be stable
}

void init_hardware(void)
{
    /* cpu freq change */
//  GPDR2 |= (1<<13);
    Voltage_GPIO();
    ChangeVoltage(VOLT_HIGH);
    ChangeFreq(freq_table[3]);

//  while(1){
//      GPSR2 |= (1<<13);
//      Delay(50*1000);
//      GPCR2 |= (1<<13);
//      Delay(50*1000);
//  }
}

void C_main(void)
{
    OSLDRINFO LoadInfo;
    ENTRYPOINT zeeldr_main;
    unsigned long packbase,packsize,flashoffset;
    unsigned long buffer = HwBufferAddress;
    long IsWriteFlash;

    LoadInfo.WriteMagic = 0;

    init_hardware();
    DBG_PortInit(0, 115200);

    WriteByte('0');
    WriteByte('1');
    WriteByte('2');
    WriteByte('3');

    // Send query signal,ask for file length
    WriteByte(queryflag);

    //  Get file length nowait
    flashoffset = ReadWordNoWait();
    packsize = ReadWordNoWait();
    IsWriteFlash = packsize & 0x80000000;
    packsize = packsize & ~0x80000000;

    if (packsize) {    //from armsys

        // end ready signal
        WriteByte(startflag);

        // receive the file data
        ReadBlock((unsigned char*)buffer, packsize);

        WriteByte(endflag);

        // jump to run program if u don't need write flash
        if (IsWriteFlash) {
            LoadInfo.WriteMagic = 0x3BADBAD3;//WRITE_MAGIC means write flash later in zeeldr
        }

        // pack is already in the dram
        packbase = buffer;
    }
    else {  //from flash
        WriteByte('4');
        WriteByte('5');
        WriteByte('6');

        // pack is on the flash memory
        packbase = HwFlashBase/* 0 */ + HwFlashOffset;

        if (IMAGE_NT_SIGNATURE != NTSIGNATURE(packbase)) return;/* Not a valid nt exe file */

        //packsize = IMAGESIZE(packbase) + 0x64;//only load the first program in the pack
        unsigned long imageOffset = (unsigned long)packbase;
        unsigned long packageOffset = *(volatile unsigned long *)(8 + imageOffset);
        PackageInfo *pPackageInfo =
            (PackageInfo *)(packageOffset + imageOffset);
        packsize = (volatile unsigned long)pPackageInfo->uPackageSize;

        //WriteNumber2(packsize);

        // load program from flash memory directly.
        //FlashReadBuffer(HwFlashBase, HwFlashOffset,
        //                  (unsigned char *)HwBufferAddress, packsize);
        fmemcpy((void*)HwBufferAddress, (void*)packbase, packsize);
        //memcpy((void*)HwBufferAddress, (void*)packbase, packsize);

        //LoadInfo.WriteMagic = 0x3BADBADB;//dummy mean load elastos from flash OK.
        //Chat();
    }

    // verify the program in buffer
    if (IMAGE_NT_SIGNATURE != NTSIGNATURE(HwBufferAddress)) {
        cprint("not a valid nt exe file\n");
        return;/* Not a valid nt exe file */
    }

    if (IMAGEBASE(HwBufferAddress) != HwBufferAddress) {
        // when no use mmu, image base always equal with its location,
        // there maybe something error, dump exe file header.
        //cprint("Warning:image base is unequal to its location!!");
        //mdump(HwBufferAddress, 0x200);
    }

    // setup loader info, will be send as paramter 2 (r1)
    LoadInfo.LoadAddress = HwBufferAddress;
    LoadInfo.PackBase = packbase;//BUGBUG: indicate pack place
    LoadInfo.PackSize = packsize;//BUGBUG: indicate pack size which had been loaded
    // setup zeeldr main func pointer
    zeeldr_main = (ENTRYPOINT)(LoadInfo.LoadAddress + ENTRYPOINT(LoadInfo.LoadAddress));

    //WriteNumber2((unsigned long)LoadInfo.LoadAddress);

    //WriteNumber2((unsigned long)zeeldr_main);

//    debugw(LoadInfo.WriteMagic);
//    debugw(LoadInfo.LoadAddress);
//    debugw(LoadInfo.PackBase);
//    debugw(LoadInfo.PackSize);
//    debugw((unsigned long)zeeldr_main);

    // Ready to run program in dram
    zeeldr_main(0, &LoadInfo);

    /* NOTREATCHED*/
    return;
}
